iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0
DevOps

30 天帶你實戰 LLMOps:從 RAG 到觀測與部署系列 第 4

Day04 - 向量資料庫(Vector Database)- 常見選項與實務比較

  • 分享至 

  • xImage
  •  

🔹 前言

在 LLMOps 的世界裡,向量資料庫 (Vector Database) 幾乎是 RAG(Retrieval-Augmented Generation) 架構的核心。

因為大語言模型(LLM)沒有「記憶」企業內部知識,我們必須把文件切片 (chunking)、轉成向量,再存進一個可以高效檢索的資料庫。 換句話說,在 RAG (Retrieval-Augmented Generation) 流程裡,向量資料庫的角色是「大腦記憶庫」:

  1. 使用者輸入問題
  2. 問題先轉成向量 (Embedding)
  3. 向量資料庫 裡找到最相關的文件片段
  4. 把文件內容交給 LLM,產生最終答案

https://ithelp.ithome.com.tw/upload/images/20250918/20120069yhv7tUpsZB.png
RAG 流程

Day 4 我們先從 向量資料庫 開始,學會怎麼存放與檢索向量;接著在 Day5 再來比較不同的 Embedding 模型,看看不同轉換方式會如何影響檢索品質。


🔹 為什麼需要向量資料庫?

傳統的關鍵字搜尋 (keyword search) 很難理解語意:

  • 搜尋「休假規則」時,可能找不到「請假流程」的文件。
  • 而 Embedding 向量能捕捉語意,讓「請假」≈「休假」,用數值來向系統表示「語意上的接近度」,而不是單純的比較字詞的重疊性。

沒有向量資料庫,RAG 就沒有辦法 以真實文件片段支撐回答,只能憑空生成,於是乎「幻覺」就出現了。除了語意檢索之外,向量資料庫會用 ANN (Approximate Nearest Neighbor) 索引[註1](如 HNSW、IVF、PQ)來在毫秒內完成搜尋,而不是單純暴力比對。

同時站在維運角度,部分專門型向量資料庫支援動態更新、可擴展與可觀測,可以讓 RAG 系統擁有真正可靠且持續進化的「長期記憶」。

[註1] Understanding the approximate nearest neighbor (ANN) algorithm
HNSW(Hierarchical Navigable Small World):用圖結構加速搜尋;
IVF(Inverted File):先把向量分桶,再在桶內搜尋;
PQ(Product Quantization):透過壓縮方式降低維度,加快比對速度。
這些索引方法犧牲了一點精確度,但可以換取大幅提升的搜尋速度


🔹 四個常見的向量資料庫功能以及收費比較

工具 型態 優點 缺點 收費 適合場景
FAISS 本地庫 (Facebook AI 開源) 輕量、快、離線可用 不支援分散式,僅限單機記憶體運算 ✅ 完全免費 個人、小專案、研究
Weaviate 開源 + 雲服務 Graph-like schema、多功能 需額外啟動容器,維運比 FAISS 複雜 本地免費;WCS 雲端有免費額度,超額需付費 想要開源 & 社群支持
Pinecone SaaS 雲端服務 無痛上手、可擴展 僅支援自家雲服務,無法自架 有免費方案(數萬向量),進入商用需付費 快速 MVP、商用
Milvus 開源 (Zilliz) + 雲服務 分散式、大規模數據 需搭配 etcd / pulsar,架構偏重 本地免費;Zilliz Cloud 有免費額度,超額需付費 海量資料 (> 億級)
  • 剛起步 / Demo → FAISS
  • 要開源 & 自己部署 → Weaviate / Milvus
  • 想省事 / 公司願意付費 → Pinecone

🔹 環境設定

經過 Day3 的環境準備,我們已經有 Docker + Conda 環境了。
今天的測試只需要額外安裝幾個客戶端:

pip install weaviate-client pinecone faiss-cpu pymilvus

GitHub Repo 裡面有打包好的 Conda 設定,也可以直接用 Conda 啟動沒問題👌


接下來我們會展示其中三個向量資料庫的操作,做這些 Demo 的目的是要 理解不同向量資料庫在真實專案中扮演的角色

🔹 Demo 1:FAISS 本地測試

FAISS 是最輕量的選項,它只會把向量暫時存在記憶體,並不具備真正的「資料庫」功能,這樣的特性讓它非常適合用來 驗證流程 / 做快速實驗

import faiss
import numpy as np

# 模擬兩個向量 (128 維)
d = 128
xb = np.random.random((5, d)).astype('float32')
xq = np.random.random((1, d)).astype('float32')

index = faiss.IndexFlatL2(d)  # L2 距離
index.add(xb)                 # 使用查詢向量 xq 建立索引
D, I = index.search(xq, k=2)  # 查詢
print("相似度距離:", D)
print("相似向量索引:", I)

執行結果:

❯ python faiss_demo.py
相似度距離: [[17.419899 17.930994]]
相似向量索引: [[0 3]]

在這個範例中,使用者的問題先被轉換成向量 xq,再透過 FAISS 在資料庫中檢索,接著在用 FAISS 建立的向量索引中進行相似度搜尋,結果顯示它與第 0 與第 3 筆文件向量最接近。實務上,會把這兩筆對應的文件片段取出,交給 LLM 生成答案。

雖然能序列化索引檔案(write_index / read_index),但依舊不適合做大型專案的持久化。


🔹 Demo 2:Weaviate 本地容器

在這個範例裡,我們不是單純在記憶體裡建一個索引,而是啟動了一個 真正的向量資料庫服務(Weaviate 容器),並且支援 schema、持久化、API 存取。

Weaviate 提供 Docker 版,可以快速啟動:

# docker-compose.yml
version: '3.4'
services:
  weaviate:
    image: semitechnologies/weaviate:1.24.2
    ports:
      - "8080:8080"
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'

啟動

docker-compose up -d

測試連線:

import weaviate  

HOST, REST_PORT, GRPC_PORT = "localhost", 8080, 50052

client = weaviate.connect_to_local(host=HOST, port=REST_PORT, grpc_port=GRPC_PORT)

嘗試插入文字:

# insert_docs.py
import numpy as np
import weaviate
from weaviate.classes.config import Property, DataType, Configure

HOST, REST_PORT, GRPC_PORT = "localhost", 8080, 50052
client = weaviate.connect_to_local(host=HOST, port=REST_PORT, grpc_port=GRPC_PORT)

try:
    print("ready?", client.is_ready())

    # 如果已經有 Docs 集合,刪掉重建
    if "Docs" in client.collections.list_all():
        client.collections.delete("Docs")

    # 建立一個 Docs collection
    client.collections.create(
        name="Docs",
        properties=[Property(name="text", data_type=DataType.TEXT)],
        vector_config=Configure.Vectors.self_provided(name="default"),
    )
    col = client.collections.get("Docs")

    # 插入兩筆資料
    dim = 128
    docs = [
        "RAG 是 Retrieval-Augmented Generation",
        "請假流程需要先主管簽核,再送人資系統",
    ]

    for text in docs:
        vec = np.random.random(dim).astype("float32").tolist()
        obj_uuid = col.data.insert({"text": text}, vector={"default": vec})
        print(f"✅ inserted: {text}, uuid={obj_uuid}")

finally:
    client.close()

執行結果:

❯ python insert_docs.py
ready? True
✅ inserted: RAG 是 Retrieval-Augmented Generation, uuid=17004906-52ba-495c-89c0-24707063bc7a
✅ inserted: 請假流程需要先主管簽核,再送人資系統, uuid=cff05618-bbca-469d-b6ef-cf2bf2217d4d

再寫另外一隻程式,嘗試隨機生成亂數,並查詢向量距離相近的文字:

❯ python query_docs.py

📦 total_count: 2
🔍 query result: [{'text': '請假流程需要先主管簽核,再送人資系統'}]

❯ python query_docs.py

📦 total_count: 2
🔍 query result: [{'text': 'RAG 是 Retrieval-Augmented Generation'}]

這證明了和 FAISS 單純記憶體索引相比,Weaviate 能把資料存起來,並透過 API 對外提供服務。


🔹 Demo 3:Pinecone 雲端測試

在 Pinecone 的範例中,我們建立了一個雲端索引,並測試與其連線。雖然這個 Demo 還沒插入具體的文件內容,但它展示了 如何在雲端建立一個可擴展的向量檢索服務

需要到 Pinecone 官網註冊帳號,然後設定環境變數:

export PINECONE_API_KEY="你的API key" #或者可以編輯.env 檔案

測試連線:

# pinecone_demo.py
from pinecone import Pinecone, ServerlessSpec
import os
import numpy as np
from dotenv import load_dotenv

# 載入 .env
load_dotenv()

# 讀取金鑰
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
if not PINECONE_API_KEY:
    raise RuntimeError("❌ 找不到 PINECONE_API_KEY,請確認 .env 或環境變數設定")

# 初始化
pc = Pinecone(api_key=PINECONE_API_KEY)
index_name = "demo-index"

# 建立 index(如果不存在)
if index_name not in [idx["name"] for idx in pc.list_indexes()]:
    pc.create_index(
        name=index_name,
        dimension=128,
        metric="cosine",
        spec=ServerlessSpec(cloud="aws", region="us-east-1"),
    )

index = pc.Index(index_name)

# 定義文件,可以在這邊修改測試資料
docs = [
    {"id": "101", "text": "員工請假需要提前三天申請"},
    {"id": "102", "text": "差旅報銷需附上發票與行程單"},
    {"id": "103", "text": "出差前需要完成出差申請表"},
]

# 先檢查目前 index 狀態
stats = index.describe_index_stats()
existing_count = stats["total_vector_count"]

if existing_count == 0:
    print("🆕 第一次執行:插入文件")
else:
    print("♻️  文件已存在:這次會更新向量內容")

# 插入 / 更新文件
vectors = []
for doc in docs:
    vec = np.random.random(128).astype("float32").tolist()
    vectors.append((doc["id"], vec, {"text": doc["text"]}))

index.upsert(vectors=vectors)

# 再檢查狀態
new_stats = index.describe_index_stats()
print(f"✅ 向量總數: {new_stats['total_vector_count']}")

# 模擬查詢
query_vec = np.random.random(128).astype("float32").tolist()
res = index.query(vector=query_vec, top_k=2, include_metadata=True)

print("\n🔍 查詢結果:")
for match in res["matches"]:
    print(f"- {match['metadata']['text']} (score={match['score']:.4f})")

執行結果:

❯ python pinecone_demo.py
♻️  文件已存在:這次會更新向量內容
✅ 向量總數: 10

🔍 查詢結果:
- 差旅報銷需附上發票與行程單 (score=0.7656)
- Cache 可以降低延遲和成本 (score=0.7636)

https://ithelp.ithome.com.tw/upload/images/20250918/20120069FQN3dQ7YPE.png

可以在雲端 console 上面觀察 metrics:

https://ithelp.ithome.com.tw/upload/images/20250918/20120069UD65ZACzGW.png

不同於 FAISS 僅存在記憶體、Weaviate 要自己架設服務,Pinecone 提供 託管式雲端服務

  • 插入文件後,資料會持久保存,下次程式再跑時不會消失。
  • 如果同一筆文件 id 重複插入,Pinecone 會自動執行 upsert(更新或新增),避免重複資料。
  • 因為是 SaaS 平台,不需要管理伺服器,插入與查詢都能即時生效,適合快速驗證或直接上線的應用。

完整可執行專案 已經放在 GitHub,完整的細節請看 README.md


🔹 Milvus (Optional)

Milvus 適合大規模應用,時間關係我不會 Demo,或是之後我再找時間補上, Milvus 可以自建,也可以使用原廠提供的雲端託管服務 (Zilliz Cloud)。


🔹 小結

今天我們

  • 認識了 四種向量資料庫,了解各自的優缺點。
  • 用三種向量庫做了相似度檢索 demo。
  • 本地可以跑 Weaviate、雲端可以測 Pinecone、大規模則考慮 Milvus。

明天(Day 5)我們將比較不同 Embedding 模型(OpenAI、HuggingFace、BGE、Cohere) 並且建立一個小型 Embedding 測試報告, 為之後的 RAG pipeline 打下基礎 🚀。

📚 引用來源 / 延伸閱讀


上一篇
Day03 - 環境準備:Docker + Conda
系列文
30 天帶你實戰 LLMOps:從 RAG 到觀測與部署4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言